package com.qingzhou.framework.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.qingzhou.common.constants.CacheConstant;
import com.qingzhou.common.constants.UserConstant;
import com.qingzhou.common.enums.DictEnum;
import com.qingzhou.common.utils.ContextUtil;
import com.qingzhou.common.utils.CryptoUtil;
import com.qingzhou.common.utils.RedisUtil;
import com.qingzhou.common.utils.SecurityUtil;
import com.qingzhou.common.web.api.ApiCodes;
import com.qingzhou.common.web.domain.LoginBody;
import com.qingzhou.common.web.domain.LoginUser;
import com.qingzhou.common.web.domain.SysUser;
import com.qingzhou.framework.dto.CurrentUser;
import com.qingzhou.system.domain.SysLoginLog;
import com.qingzhou.system.service.ISysLoginLogService;
import com.qingzhou.system.service.ISysMenuService;
import com.qingzhou.system.service.ISysRoleService;
import com.qingzhou.system.service.ISysUserService;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 认证处理
 *
 * @author xm
 */
@Component
public class LoginService {

    /** 登录成功 */
    public static final String LOGIN_SUCCESS = "Success";
    /** 登录失败 */
    public static final String LOGIN_FAIL = "Error";
    /** 注销 */
    public static final String LOGOUT = "Logout";
    /** 注册 */
    public static final String REGISTER = "Register";

    @Inject
    CaptchaService captchaService;

    @Inject
    RedisUtil redisUtil;

    @Inject
    ISysUserService sysUserService;

    @Inject
    ISysRoleService sysRoleService;

    @Inject
    ISysMenuService sysMenuService;

    @Inject
    ISysLoginLogService sysLoginLogService;

    /** 登录 */
    public Map<String, Object> login(LoginBody loginBody) {
        // 验证码校验
        captchaService.checkCaptcha(loginBody.getCode(), loginBody.getUuid());

        // 校验用户名密码，并解密密码
        String username = loginBody.getUsername();
        String password = checkUserNameAndPassword(username, loginBody.getPassword());

        // 校验登录失败
        checkLoginError(username);

        // 查询用户
        SysUser sysUser = sysUserService.selectSysUserByUserName(username);
        // 校验登录用户
        checkLoginUser(username, password, sysUser);

        // 以上校验通过后，登录成功
        return loginSuccess(sysUser);
    }

    /**
     * 获取登录用户信息
     */
    public CurrentUser getUserInfo() {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            throw ApiCodes.CODE_400("登录失效");
        }
        loginUser.getSysUser().setPassword(StrUtil.EMPTY);  // 清空密码
        return CurrentUser.of(loginUser);
    }

    /**
     * 登出
     */
    public void logout() {
        String userName = SecurityUtil.getUserName();
        recordLoginLog(userName, LOGIN_FAIL, "登出成功");
        SecurityUtil.logout();
    }

    /** 校验用户名和密码 */
    private String checkUserNameAndPassword(String username, String password) {
        // 账号密码非空
        if (!StrUtil.isAllNotEmpty(username, password)) {
            throw ApiCodes.CODE_400("账号/密码必须填写");
        }
        // 解密密码
        password = CryptoUtil.rsaDecrypt(password);
        // 密码是否在指定长度范围内
        if (password.length() < UserConstant.PASSWORD_MIN_LENGTH || password.length() > UserConstant.PASSWORD_MAX_LENGTH) {
            throw ApiCodes.CODE_400("密码长度不在指定范围");
        }
        // 用户名是否在指定长度范围内
        if (username.length() < UserConstant.USERNAME_MIN_LENGTH || username.length() > UserConstant.USERNAME_MAX_LENGTH) {
            throw ApiCodes.CODE_400("账号长度不在指定范围");
        }
        return password;
    }

    /** 校验登录失败 */
    private void checkLoginError(String username) {
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;

        Long errCount = redisUtil.getLong(loginErrorKey,0L);

        if (errCount >= CacheConstant.LOGIN_ERROR_TOTAL) {
            // 超过最大重试次数, 账号锁定, 需要等待
            long milSecondsToWait = redisUtil.getCacheObjectExpire(loginErrorKey);
            long minutesToWait = milSecondsToWait / 1000 / 60 + 1L;
            throw ApiCodes.CODE_400(String.format("账号已锁定，请 %s 分钟后再试", minutesToWait));
        }

    }

    /** 处理登录失败 */
    private void handleLoginError(String username) {
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;

        long errCountNew = redisUtil.getLong(loginErrorKey, 0L) + 1;

        redisUtil.setCacheObject(loginErrorKey, errCountNew, Duration.ofMinutes(CacheConstant.LOGIN_ERROR_EXPIRATION));

        if (errCountNew >= CacheConstant.LOGIN_ERROR_TOTAL) {
            throw ApiCodes.CODE_400(String.format("账号已锁定，请 %s 分钟后再试", CacheConstant.LOGIN_ERROR_EXPIRATION));
        } else {
            long n = CacheConstant.LOGIN_ERROR_TOTAL - errCountNew;
            throw ApiCodes.CODE_400(String.format("账号或密码错误，还剩 %s 次机会", n));
        }
    }

    /** 校验登录用户 */
    private void checkLoginUser(String username, String password, SysUser sysUser) {
        if (ObjectUtil.isNull(sysUser)) {
            handleLoginError(username);
        }
        if (DictEnum.DEL_FLAG.DELETED.getValue() == sysUser.getDelFlag()) {
            String msg = "对不起，您的账号已被删除";
            recordLoginLog(username, LOGIN_FAIL, msg);
            throw ApiCodes.CODE_400(msg);
        }
        if (DictEnum.COMMON_STATUS.DISABLE.getValue().equals(sysUser.getStatus())) {
            String msg = "账号已停用，请联系管理员";
            recordLoginLog(username, LOGIN_FAIL, msg);
            throw ApiCodes.CODE_400(msg);
        }
        // 密码是否正确
        if (!SecurityUtil.matchesPassword(password, sysUser.getPassword())) {
            recordLoginLog(username, LOGIN_FAIL, "密码错误");
            handleLoginError(username);
        }
    }

    /**
     * 登录成功
     */
    private Map<String, Object> loginSuccess(SysUser sysUser) {
        LoginUser loginUser = new LoginUser();
        loginUser.setSysUser(sysUser);
        loginUser.setUserid(sysUser.getUserId());
        String username = sysUser.getUserName();
        loginUser.setUsername(username);
        loginUser.setIpaddr(ContextUtil.getIpAddr());
        // 角色集合
        Set<String> roles = sysRoleService.getRolePermission(sysUser.getUserId());
        loginUser.setRoles(roles);

        // 权限集合
        Set<String> permissions = sysMenuService.getMenuPermission(sysUser);
        loginUser.setPermissions(permissions);

        LoginUser login = SecurityUtil.login(loginUser);
        // 删除登录失败
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + loginUser.getUsername();
        redisUtil.deleteCacheObject(loginErrorKey);

        recordLoginLog(username, LOGIN_SUCCESS, "登录成功");

        Map<String, Object> map = new HashMap<>();
        map.put("access_token", login.getToken());
        map.put("expires_in", login.getExpireTime());
        return map;
    }

    /**
     * 记录登录日志
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    public void recordLoginLog(String username, String status, String message) {
        SysLoginLog sysLoginLog = new SysLoginLog();
        sysLoginLog.setUserName(username);
        sysLoginLog.setIpaddr(ContextUtil.getIpAddr());
        sysLoginLog.setRemark(message);
        // 日志状态
        if (StrUtil.equalsAny(status, LOGIN_SUCCESS, LOGOUT, REGISTER)) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.SUCCESS.getValue());
        } else if (LOGIN_FAIL.equals(status)) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.FAIL.getValue());
        }
        sysLoginLogService.save(sysLoginLog);
    }

}
